# -*- coding: utf-8 -*- 
# Author: Jacky
# Creation Date: 2021/3/8


import os
import pandas as pd
import numpy as np
import random
from collections import namedtuple
from collections import Counter
# 创建了一个名为 BuiltinData_set 的命名元组（namedtuple），用于表示内置数据集的信息。命名元组是一个不可变的、命名的元组子类，它允许通过名称而不是索引来访问其元素。
BuiltinData_set = namedtuple('BuiltinData_set', ['url', 'path', 'sep', 'reader_params'])

BUILTIN_DATA_SETS = {
    'ml-100k':
        BuiltinData_set(
            url='http://files.grouplens.org/data_sets/movielens/ml-100k.zip',
            path='data/ml-100k/u.data', #用户评分信息
            sep='\t',
            reader_params=dict(line_format='user item rating timestamp', # 指定了数据文件每行的格式。在这里，数据文件中每一行包含四个字段：用户ID、项目ID、评分和时间戳。
                               rating_scale=(1, 5), # 指定了评分的范围，即评分的最小值和最大值
                               sep='\t')
        ),
    'ml-1m':
        BuiltinData_set(
            url='http://files.grouplens.org/data_sets/movielens/ml-1m.zip',
            path='data/ml-1m/ratings.dat',
            sep='::', # 文件中的数据是用双冒号分隔
            reader_params=dict(line_format='user item rating timestamp',
                               rating_scale=(1, 5),
                               sep='::')
        ),
}


class DataSet:
    """Base class for loading data_sets.

    Note that you should never instantiate the :class:`Data_set` class directly
    (same goes for its derived classes), but instead use one of the below
    available methods for loading data_sets."""

    def __init__(self):
        pass
    # 返回数据集ml-100k处理后的dataframe，根据user_id排序，去掉time_stamp
    @classmethod
    def LoadDataSet(cls, name='ml-100k'): # 加载内置数据集
        """Load a built-in data_set.

        :param name:string: The name of the built-in data_set to load.
                Accepted values are 'ml-100k', 'ml-1m', and 'jester'.
                Default is 'ml-100k'.
        :return: ratings for each line.
        """
        try:
            data_set = BUILTIN_DATA_SETS[name] # 根据给定的名称获取对应的数据集信息
        except KeyError: # 如果给定的数据集名称不在字典中，则抛出 ValueError 异常
            raise ValueError('unknown data_set ' + name +
                             '. Accepted values are ' +
                             ', '.join(BUILTIN_DATA_SETS.keys()) + '.')
        if not os.path.isfile(data_set.path): # 如果数据集文件不存在，则抛出 OSError 异常
            raise OSError( # OSError 异常可以包括诸如文件不存在、权限错误、文件系统已满等等与操作系统相关的问题
                "Data_set data/" + name + " could not be found in this project.\n"
                                          "Please download it from " + data_set.url +
                ' manually and unzip it to data/ directory.')
        ratings_header = ['user_id', 'movie_id', 'rating', 'timestamp']
        # names=ratings_header 的作用是告诉 pd.read_csv() 函数，读取 CSV 文件时应该使用 ratings_header 列表中的列名。这样读取后的 DataFrame 就会使用指定的列名，而不是默认的列索引作为列标签。
        ratings = pd.read_csv(data_set.path, sep=data_set.sep, header=None, names=ratings_header, engine='python',
                              encoding='latin-1')
        ratings = ratings.drop(['timestamp'], axis=1)  # 删除timestamp
        ratings['rating'] = (ratings['rating']) / 5  # 归一化处理
        if name == 'ml-100k':
            # 如果数据集是ml-100k则对数据集进行重新排序，根据用户id
            ratings = ratings.sort_values(by='user_id', ignore_index=True)
        print("Load " + name + " data_set success.\n")
        return ratings

    # 未调用
    @classmethod
    def SplitTrainTest(cls, ratings, test_size=0.2):
        """
        Split rating data to training set and test set.

        The default `test_size` is the test percentage of test size.

        The rating file should be a instance of DataSet.

        :param ratings: raw data_set (dataFrame)
        :param test_size: the percentage of test size.
        :return: train_set and test_set
        """

        train_list = []
        test_list = []
        train_set_len = 0
        test_set_len = 0

        for num in np.arange(0, ratings.shape[0]):
            if random.random() <= test_size:
                test_list.extend(ratings.iloc[num])
                test_set_len += 1
            else:
                train_list.extend(ratings.iloc[num])
                train_set_len += 1
        # 得到train和test的数组形式 并对其进行reshape
        # 6代表着user_id|movie_id|rating|gender|age|occupation|label
        train_array = np.array(train_list, dtype=float)
        train_array = train_array.reshape((int(train_array.shape[0] / 7), 7))
        test_array = np.array(test_list, dtype=float)
        test_array = test_array.reshape((int(test_array.shape[0] / 7), 7))
        # 将array转换为dataFrame
        train = pd.DataFrame(train_array)
        test = pd.DataFrame(test_array)
        print('split rating data to training set and test set success.')
        print('train set size = %s' % train_set_len)
        print('test set size = %s' % test_set_len)
        # return train, test, train_array, test_array
        return train_array, test_array


def convert(data_set, movie_max): # data_set必须为一个numpy数组
    """
    将数据集转换为一个数组，其中每行代表一个用户，每列代表一个电影。
    movie_max：电影数量。
    data_set：输入数组，第一列是用户，第二列是电影，第三列是评分。
    返回：一个数组，每行代表一个用户，每列代表一个电影。
    """
    new_data_set = []
    # 从数据集中获取所有不重复的用户ID，并将它们存储在名为 user_in_dataset 的 numpy 数组中，data_set[:, 0]：表示从数据集中选择所有行的第一列，np.unique()：这是 numpy 库中的一个函数，用于获取数组中的唯一值，即去重操作。它返回数组中的唯一值，并按升序排列
    user_in_dataset = np.unique(data_set[:, 0].astype(np.uint16))
    #这块的代码是用来
    # Movie_count = dict(Counter(data_set[:, 1]))
    # key, value, value_percent = [], [], []
    # for item in Movie_count.items():
    #     key.append(item[0])
    #     value.append(item[1])
    # for ind in range(len(value)):
    #     percent = value[ind]/sum(value)
    #     value_percent.append(percent)
    # for i in range(len(data_set)):
    #     for j in range(len(key)):
    #        if data_set[i, 1] == key[j]:
    #           data_set[i, 2] = 0.9*data_set[i, 2] + 0.1*value_percent[j]
    #           break
    ##用来计算每个client内的内容流行度的
    for id_users in user_in_dataset:
        # 对于每个用户 ID，获取与之相关的电影 ID，并转换为 numpy 数组
        id_movies = data_set[:, 1][data_set[:, 0] == id_users].astype(np.uint16)
        # 获取每个用户对应的评分
        id_ratings = data_set[:, 2][data_set[:, 0] == id_users]
        # ratings第一列保存用户id， 后movie_max列为对应电影索引，最后三列为gender、age、occupation
        # 创建一个全零一维数组，长度为电影数量加上额外的三列，分别用来保存用户 ID、性别、年龄和职业
        ratings = np.zeros(movie_max + 1 + 3)
        ratings[0] = id_users
        ratings[id_movies] = id_ratings
        ratings[[-1, -2, -3]] = data_set[:, [-1, -2, -3]][data_set[:, 0] == id_users][0] # 最后加[0]是因为最后三列信息都是重复的，䘝就可以
        # 将每个用户的信息存储到新的二维数组中
        new_data_set.append(list(ratings))
    new_data_set = np.array(new_data_set, dtype=float)
    # 返回新的numpy二维数组
    return new_data_set




'''如： 
   原始：User ID | Movie ID | Rating | Gender | Age | Occupation
-------------------------------------------------------
   1    |    101   |   4.0   |   M    |  25 |    2
   1    |    105   |   3.5   |   M    |  25 |    2
   2    |    103   |   5.0   |   F    |  30 |    1
   2    |    105   |   4.0   |   F    |  30 |    1
   3    |    101   |   4.5   |   M    |  35 |    3
   3    |    102   |   3.0   |   M    |  35 |    3
转换后：
[ [ 1, 4.0, 0, 3.5, 0, 0, 25, 'M', 2],
 [ 2, 0, 5.0, 0, 4.0, 0, 30, 'F', 1],
 [ 3, 4.5, 3.0, 0, 0, 0, 35, 'M', 3]
]
每一行代表一个用户，每一列表示一个电影，后三列为。。。
   '''